package com.egl.web;

import java.io.IOException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;

import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.h2.jdbcx.JdbcDataSource;

//http://www.mnot.net/blog/2007/05/15/expires_max-age
public class CacheControlHeaderFilter implements Filter {

	private static ServletContext SERVLET_CONTEXT = null;

	private static DataSource DS = null;

	private static WebServer WEB_SERVER = null;

	protected static abstract class _AbstractHttpCacheControl {

		protected Short week = null;
		protected Short day = null;
		protected Byte hour = null;
		protected Byte minute = null;
		protected Byte second = null;

		protected int getTotalSecond() {
			int i = 0;
			if (week != null) {
				i += week.intValue() * 604800;
			}
			if (day != null) {
				i += day.intValue() * 86400;
			}
			if (hour != null) {
				i += hour.intValue() * 3600;
			}
			if (minute != null) {
				i += minute.intValue() * 60;
			}
			if (second != null) {
				i += second.intValue();
			}
			return i;
		}
	}

	private static class _URLHttpCacheControl extends _AbstractHttpCacheControl {
		private String url = null;
	}

	private static class _ExtensionHttpCacheControl extends
			_AbstractHttpCacheControl {
		private String extension = null;
	}

	private String dataSQL = null;

	public void init(FilterConfig filterConfig) throws ServletException {
		SERVLET_CONTEXT = filterConfig.getServletContext();
		DS = createDataSource();
		//
		String webServerClass = null;
		webServerClass = filterConfig.getInitParameter("webServerClass");
		WEB_SERVER = createWebServer(webServerClass, SERVLET_CONTEXT);
		//
		dataSQL = filterConfig.getInitParameter("dataSQL");
		//
		initDatabaseFromResource(DS, "http_cache_control_definition.sql");
		initDatabaseFromResource(DS, dataSQL);
	}

	private DataSource createDataSource() {
		JdbcDataSource ds = null;
		ds = new JdbcDataSource();
		ds.setURL("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1");
		return ds;
	}

	private WebServer createWebServer(String className, ServletContext context) {
		WebServer webServer = null;
		webServer = WebServerFactory.createDefaultWebServerIfFails(className);
		webServer.setServletContext(context);
		return webServer;
	}

	private void initDatabaseFromResource(DataSource ds, String path) {
		if (StringUtils.isBlank(path)) {
			return;
		}
		InputStream is = null;
		List<String> lines = null;
		String sql = null;
		is = getClass().getResourceAsStream('/' + path);
		if (is == null) {
			return;
		}
		try {
			lines = IOUtils.readLines(is);
			sql = StringUtils.join(lines, '\n');
			initDatabaseFromSQL(ds, sql);
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			IOUtils.closeQuietly(is);
		}
	}

	private void initDatabaseFromSQL(DataSource ds, String sql) {
		Connection connection = null;
		Statement s = null;
		try {
			connection = ds.getConnection();
			s = connection.createStatement();
			s.execute(sql);
		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			DbUtils.closeQuietly(s);
			DbUtils.closeQuietly(connection);
		}

	}

	public void doFilter(ServletRequest req, ServletResponse res,
			FilterChain chain) throws IOException, ServletException {
		HttpServletRequest request = null;
		HttpServletResponse response = null;
		if (req instanceof HttpServletRequest) {
			request = (HttpServletRequest) req;
		}
		if (res instanceof HttpServletResponse) {
			response = (HttpServletResponse) res;
		}
		if (request != null && response != null) {
			_doFilter(request, response);
		}// if

		chain.doFilter(req, res);
	}

	private void _doFilter(HttpServletRequest request,
			HttpServletResponse response) {

		//
		String requestURI = null;
		requestURI = request.getRequestURI();

		String url = null;
		String contextPath = null;
		contextPath = WEB_SERVER.getContextPath();
		if (!StringUtils.endsWith(contextPath, "/")) {
			contextPath = contextPath + '/';
		}
		url = StringUtils.substringAfter(requestURI, contextPath);

		//
		if (StringUtils.isNotBlank(url)) {
			_AbstractHttpCacheControl ahcc = null;
			ahcc = getHttpCacheControl(DS, url);
			if (ahcc != null) {
				setupCacheControlHeader(response, ahcc);
			}// if
		}// if

	}

	private void setupCacheControlHeader(HttpServletResponse response,
			_AbstractHttpCacheControl ahcc) {
		if (response == null || ahcc == null) {
			return;
		}
		response.setHeader("Cache-Control", "max-age=" + ahcc.getTotalSecond());
	}

	private _AbstractHttpCacheControl getHttpCacheControl(DataSource ds,
			String url) {

		String extension = null;
		extension = StringUtils.substringAfterLast(url, ".");

		//
		boolean urlMapContains = false;
		boolean extMapContains = false;

		urlMapContains = HTTP_CACHE_CONTROL_MAP_URL.containsKey(url);
		extMapContains = HTTP_CACHE_CONTROL_MAP_EXTENSION
				.containsKey(extension);

		//
		_AbstractHttpCacheControl ahcc = null;

		if (!urlMapContains && !extMapContains) {
			// get HttpCacheControl by url
			ahcc = _getHttpCacheControlByURL(ds, url);
			// get HttpCacheControl by extension
			if (ahcc == null) {
				ahcc = _getHttpCacheControlByExtension(ds, extension);
			}
		} else {
			ahcc = HTTP_CACHE_CONTROL_MAP_URL.get(url);
			if (ahcc == null) {
				ahcc = HTTP_CACHE_CONTROL_MAP_EXTENSION.get(extension);
			}
		}// if

		return ahcc;

	}

	private static final Map<String, _URLHttpCacheControl> HTTP_CACHE_CONTROL_MAP_URL = new HashMap<String, _URLHttpCacheControl>();

	private _URLHttpCacheControl _getHttpCacheControlByURL(DataSource ds,
			String url) {
		_URLHttpCacheControl uhcc = null;
		uhcc = HTTP_CACHE_CONTROL_MAP_URL.get(url);
		if (uhcc == null) {
			uhcc = getHttpCacheControlByURL(ds, url);
			// if (uhcc != null) {
			HTTP_CACHE_CONTROL_MAP_URL.put(url, uhcc);
			// }
		}
		return uhcc;
	}

	private _URLHttpCacheControl getHttpCacheControlByURL(DataSource ds,
			String url) {
		_URLHttpCacheControl hcc = null;
		Connection connection = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			connection = ds.getConnection();
			ps = connection
					.prepareStatement("select url,week,day,hour,minute,second from url_http_cache_control where url=?");
			ps.setString(1, url);

			rs = ps.executeQuery();
			while (rs != null && rs.next()) {
				hcc = new _URLHttpCacheControl();
				hcc.url = rs.getString("url");
				populateAbstractHttpCacheControl(rs, hcc);
			}// while

		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			DbUtils.closeQuietly(connection, ps, rs);
		}
		return hcc;
	}

	private static final Map<String, _ExtensionHttpCacheControl> HTTP_CACHE_CONTROL_MAP_EXTENSION = new HashMap<String, _ExtensionHttpCacheControl>();

	private _AbstractHttpCacheControl _getHttpCacheControlByExtension(
			DataSource ds, String url) {
		_ExtensionHttpCacheControl ehcc = null;
		ehcc = HTTP_CACHE_CONTROL_MAP_EXTENSION.get(url);
		if (ehcc == null) {
			ehcc = getHttpCacheControlByExtension(ds, url);
			// if (ehcc != null) {
			HTTP_CACHE_CONTROL_MAP_EXTENSION.put(url, ehcc);
			// }
		}
		return ehcc;
	}

	private _ExtensionHttpCacheControl getHttpCacheControlByExtension(
			DataSource ds, String extension) {
		_ExtensionHttpCacheControl ecc = null;
		Connection connection = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try {
			connection = ds.getConnection();
			ps = connection
					.prepareStatement("select extension,week,day,hour,minute,second from extension_http_cache_control where extension=?");
			ps.setString(1, extension);

			rs = ps.executeQuery();
			while (rs != null && rs.next()) {
				ecc = new _ExtensionHttpCacheControl();
				ecc.extension = rs.getString("extension");
				populateAbstractHttpCacheControl(rs, ecc);
			}// while

		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			DbUtils.closeQuietly(connection, ps, rs);
		}
		return ecc;
	}

	private void populateAbstractHttpCacheControl(ResultSet rs,
			_AbstractHttpCacheControl ahcc) throws SQLException {
		Object obj = null;
		obj = rs.getObject("week");
		if (obj instanceof Short) {
			ahcc.week = (Short) obj;
		}
		// day
		obj = rs.getObject("day");
		if (obj instanceof Short) {
			ahcc.day = (Short) obj;
		}
		// second
		obj = rs.getObject("hour");
		if (obj instanceof Byte) {
			ahcc.hour = (Byte) obj;
		}
		// second
		obj = rs.getObject("minute");
		if (obj instanceof Byte) {
			ahcc.minute = (Byte) obj;
		}
		// second
		obj = rs.getObject("second");
		if (obj instanceof Byte) {
			ahcc.second = (Byte) obj;
		}
	}

	public void destroy() {
		shutdownDatasource(DS);
	}

	private void shutdownDatasource(DataSource dataSource) {
		if (dataSource == null) {
			return;
		}
		Connection connection = null;
		PreparedStatement ps = null;
		Statement st = null;
		ResultSet rs = null;
		String str = null;
		// list of table
		List<String> tables = null;
		try {
			connection = dataSource.getConnection();
			ps = connection
					.prepareStatement("SELECT table_name FROM information_schema.tables where table_schema=?;");
			ps.setString(1, "PUBLIC");
			rs = ps.executeQuery();
			while (rs != null && rs.next()) {
				str = rs.getString("table_name");
				if (StringUtils.isBlank(str)) {
					continue;
				}
				if (tables == null) {
					tables = new ArrayList<String>();
				}// if
				tables.add(str);
			}// while

			for (int i = 0; tables != null && i < tables.size(); i++) {
				str = tables.get(i);
				if (StringUtils.isBlank(str)) {
					continue;
				}// if
				if (st == null) {
					st = connection.createStatement();
				}
				st.execute("truncate table " + str);
			}// for

			st.execute("shutdown");

		} catch (SQLException e) {
			e.printStackTrace();
		} finally {
			DbUtils.closeQuietly(st);
			DbUtils.closeQuietly(connection, ps, rs);
			DS = null;
		}
	}

}